import * as c from 'colors'
import { Callback, createClient, RedisClient } from 'redis'
import { EventEmitter } from '../../helpers/EventEmitter'
import { redisConfig } from '../../scretConfig'

interface EventArgs { msg: string, code: number }

interface EmittableEvents {
  connect: EventArgs
  ready: EventArgs
  error: EventArgs
}

/** for select */
function promisify<T>(context: RedisClientEnhance, fn: (index: number, cb: Callback<T>) => void): (index: number) => Promise<T>
/** for get */
function promisify<T>(context: RedisClientEnhance, fn: (key: string, cb: Callback<T>) => void): (key: string) => Promise<T>
/** for incrby|incrbyFloat|expire */
function promisify<T>(context: RedisClientEnhance, fn: (key: string, incrementOrSeconds: number, cb: Callback<T>) => void): (key: string, incrementOrSeconds: number) => Promise<T>
/** for set|hget */
function promisify<T>(context: RedisClientEnhance, fn: (key: string, valueOrFiled: string, cb: Callback<T>) => void): (key: string, valueOrFiled: string) => Promise<T>
/** for hincrby */
function promisify<T>(context: RedisClientEnhance, fn: (key: string, filed: string, increment: number, cb: Callback<T>) => void): (key: string, filed: string, increment: number) => Promise<T>
/** for hset */
function promisify<T>(context: RedisClientEnhance, fn: (key: string, filed: string, value: string, cb: Callback<T>) => void): (key: string, filed: string, value: string) => Promise<T>
/** for hmset */
function promisify<T>(context: RedisClientEnhance, fn: (key: string, ...valueOrCallback: Array<string | Callback<T>>) => void): (key: string, ...values: string[]) => Promise<T>
function promisify<T>(context: RedisClientEnhance, fn: (...args: any[]) => void): (...args: any[]) => Promise<T> {
  return function (...args: any[]) {
    return new Promise<T>((resolve, reject) => {
      let realArgs = []
      for (let i = 0; i < args.length; i++) {
        realArgs.push(args[i])
      }
      fn.call(context.getRedisInstance(), ...realArgs, function (err: Error | null, res: any) {
        if (err) {
          reject(err)
        } else {
          resolve(res)
        }
        context.release()
      })
    })
  }
}

export class RedisClientEnhance extends EventEmitter<EmittableEvents> {
  private client!: RedisClient
  // NOTE: 已占用的(表示改链接正在使用)
  private occupied: boolean = false
  constructor() {
    super()
    this.init()
  }

  public isAvailable() {
    return this.occupied
  }

  public occupy(): this {
    this.occupied = true
    return this
  }

  public release(): this {
    this.occupied = false
    return this
  }

  public getRedisInstance() {
    return this.client
  }

  public do() {
    return {
      select: promisify(this, this.client.select),
      set: promisify(this, this.client.set) as (key: string, value: string) => Promise<string>,
      get: promisify(this, this.client.get),
      hget: promisify(this, this.client.hget) as (key: string, filed: string) => Promise<string>,
      hset: promisify(this, this.client.hset),
      hdel: promisify(this, this.client.hdel) as (key: string, filed: string) => Promise<number>,
      hgetall: promisify(this, this.client.hgetall),
      incr: promisify(this, this.client.incr),
      incrby: promisify(this, this.client.incrby) as (key: string, increment: number) => Promise<number>,
      hincrby: promisify(this, this.client.hincrby) as (key: string, filed: string, increment: number) => Promise<number>,
      expire: promisify(this, this.client.expire) as (key: string, seconds: number) => Promise<number>,
      hmset: promisify(this, this.client.hmset) as (key: string, ...values: string[]) => Promise<string>,
      hmget: promisify(this, this.client.hmget) as (key: string, ...values: string[]) => Promise<string[]>,
      lpush: promisify(this, this.client.lpush) as (key: string, ...values: string[]) => Promise<number>,
      keys: promisify(this, this.client.keys),

    }
  }

  private init() {
    if (this.client && this.client.connected) {
      return
    }
    this.client = createClient(redisConfig)
    this.onEvents()
  }

  private onEvents() {
    this.client.on('connect', () => {
      const date = new Date()
      console.log(c.green('[ Redis Connect Success ]'))
      this.emit('connect', { code: 0, msg: `[ Redis Connect Success ]: ${date}` })
    })
    this.client.on('ready', () => {
      const date = new Date()
      console.log(c.green('Redis Ready'))
      this.emit('ready', { code: 0, msg: `[ Redis Connect Ready ]: ${date}` })
    })
    this.client.on('error', (e) => {
      console.log(e)
      const date = new Date()
      console.log(c.red('Redis Connect Error'))
      this.client.quit()
      this.emit('error', { code: -1, msg: `[ Redis Connect Error ]: ${date}` })
    })
  }

}
